/**
 * com.chaeron.GPS - An API for access to GPS devices.
 *
 *
 * Copyright (c) 2001, 2002 Chaeron Corporation,
 * All rights reserved.
 * 
 * Chaeron grants you a non-exclusive license to use, modify and re-distribute
 * this program under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version, provided that this copyright notice and 
 * license appear on all copies of the software.
 * 
 * Software is provided "AS IS," without a warranty of any kind. ALL EXPRESS OR
 * IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
 * HEREBY EXCLUDED. THE ENTIRE RISK ARISING OUT OF USING THE SOFTWARE IS ASSUMED
 * BY THE LICENSEE. See the GNU General Public License for more details.
 *
 * CHAERON AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
 * LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR DISTRIBUTING SOFTWARE.
 * IN NO EVENT WILL CHAERON OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE,
 * PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL
 * OR PUNITIVE DAMAGES, HOWEVER CAUSED AN REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF CHAERON HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @version     1.02
 * @author  	Andrzej Jan Taramina, Chaeron Corporation
 *  			andrzej@chaeron.com
 */

package com.chaeron.platform.common;

import java.io.*;

import com.chaeron.platform.common.*;

/**
 * Catalog is a collection of records commonly referred to as a database
 * on small devices.
 * <p>
 * Here is an example showing data being read from records in a catalog:
 *
 * <pre>
 * Catalog c = new Catalog("CRTR.TYPE", Catalog.READ_ONLY);
 * if (!c.isOpen())
 *   return;
 * int count = c.getRecordCount();
 * byte b[] = new byte[10];
 * for (int i = 0; i < count; i++)
 *   {
 *   if (c.setRecord(i) != true)
 *     continue; // record is deleted
 *   c.readBytes(b, 0, 10);
 *   ...
 *   }
 * c.close();
 * </pre>
 */

public class Catalog implements Stream
{
	/** Read-only open mode. */
	public static final int READ_ONLY  = 1;
	/** Write-only open mode. */
	public static final int WRITE_ONLY = 2;
	/** Read-write open mode. */
	public static final int READ_WRITE = 3; // READ | WRITE
	/** Create open mode. Used to create a database if one does not exist. */
	public static final int CREATE = 4;
	/** Is the Catalog open? */
	private boolean open=false;
	
	/** The file for the Catalog */
	private java.io.File file;
	
	/** The records in the Catalog */
	private Vector records;
	
	/** The index of the currently selected record */
	private int currentIndex;
	
	/** The currently selected record */
	private byte[] currentRecord;
	
	/** The byte position in the current record */
	private int pos;
	
	/** The mode we were opened in */
	private int mode;
	
	/**
	 * Opens a catalog with the given name and mode. If mode is CREATE, the
	 * database will be created if it does not exist. The name must be exactly
	 * 9 characters.
	 * <p>
	 * For PalmOS: The first 4 characters in the name specify the database's
	 * creator. If you want the database to be deleted when your program is
	 * deinstalled, it should have the same creator ID as your program's
	 * creator ID. If you are using exegen, the creator ID is generated
	 * automatically from your program name. When the exegen program is executed
	 * it will display the creator id it generates. The 5th character must
	 * be a period. The 6th through 9th characters specify the type of
	 * database.
	 * <p>
	 * Here is an example:
	 * <pre>
	 * // creator is AbCD and type is TYPE
	 * Catalog c = new Catalog("AbCD.TYPE", Catalog.CREATE);
	 * </pre>
	 * @param name 9 character database identifier
	 * @param mode open/create mode
	 */
	
	public Catalog( String name, int mode )
	{
		this.mode = mode;
		
		file = new java.io.File( name );
		if( file.exists() && mode != CREATE ) {
			try {
				BufferedReader input = new BufferedReader( new FileReader( file ) );
				
				records = new Vector();
				
				String row = input.readLine();
				
				while( row != null ) {
					row = row + "\r\n";
					records.add( row.getBytes() );
					row = input.readLine();
				}
				
				input.close();
				open = true;
			}
			catch( IOException e ) {
				e.printStackTrace();
				open = false;
			}
		} else {
			if( mode == CREATE ) {
				records = new Vector();
				open = true;
			} else {
				open = false;
			}
		}
		
		currentIndex = -1;
	}
	
	/**
	 * Adds a record to the end of the catalog. If this operation is successful,
	 * the position of the new record is returned and the current position is
	 * set to the new record. If it is unsuccessful the current position is
	 * unset and -1 is returned.
	 * @param size the size in bytes of the record to add
	 */
	public int addRecord( int size )
	{
		int	pos	= -1;
		
		if( open ) {
			byte[] buf = new byte[ size ];
			records.add( buf );
			pos = records.getCount() - 1;
			setRecordPos( pos );
		}
		
		return( pos );
	}
	
	/**
	 * Closes the catalog. Returns true if the operation is successful and false
	 * otherwise.
	 */
	public void close()
	{
		boolean ret 	= false;
		
		if( open ) {
			try{
				if( mode != READ_ONLY ) {
					BufferedWriter output = new BufferedWriter( new FileWriter( file ) );
					int size = records.getCount();
					for( int i = 0; i < size; i++ )
					{
						output.write( new String( (byte[])records.get( i ) ) );
					}
					output.close();
				}
				open = false;
				ret = true;
			}
			catch(IOException e)
			{
				e.printStackTrace();
			}
		}
		
		// return( ret );
	}
	
	/**
	 * Deletes the catalog. Returns true if the operation is successful and false
	 * otherwise. Under PalmOS, this operation simply marks the record for deletion.
	 * The record will actually be removed by the operating system at some later
	 * time. Under WindowsCE, the record is immediatly removed.
	 */
	public boolean delete()
	{
		return file.delete();
	}
	
	/**
	 * Deletes the current record and sets the current record position to -1.
	 * A call to setRecordPos() for a deleted record will fail under PalmOS
	 * until the operating system actually frees up the record.
	 */
	public boolean deleteRecord()
	{
		boolean ret = false;
		
		if( open ) {
			records.del( currentIndex );
			currentIndex = -1;
			ret = true;
		}
		
		return( ret );
	}
	
	/**
	 * Returns the number of records in the catalog or -1 if the catalog is not open.
	 */
	public int getRecordCount()
	{
		return( records.getCount() );
	}
	
	/**
	 * Returns the size of the current record in bytes or -1 if there is no
	 * current record.
	 */
	public int getRecordSize()
	{
		int size = -1;
		
		if( open && currentIndex != -1 ) {
			size = currentRecord.length;
		}
		
		return( size );
	}
	
	/**
	 * Returns true if the catalog is open and false otherwise. This can
	 * be used to check if opening or creating a catalog was successful.
	 */
	public boolean isOpen()
	{
		return( open );
	}
	
	/**
	 * Sets the current record position and locks the given record. The value
	 * -1 can be passed to unset and unlock the current record. If the operation
	 * is succesful, true is returned and the read/write cursor is set to
	 * the beginning of the record. Otherwise, false is returned (which
	 * usually indicates the record has been deleted).
	 */
	public boolean setRecordPos( int pos )
	{
		boolean ret	= false;
		
		if( open ) {
			currentIndex = pos;
			if( pos != -1 ) {
				currentRecord = (byte[])records.get( currentIndex );
				this.pos = 0;
			}
			ret = true;
		}
		
		return( ret );
	}
	
	/**
	 * Reads bytes from the current record into a byte array. Returns the
	 * number of bytes actually read or -1 if an error prevented the
	 * read operation from occurring. After the read is complete, the location of
	 * the cursor in the current record (where read and write operations start from)
	 * is advanced the number of bytes read.
	 * @param buf the byte array to read data into
	 * @param start the start position in the array
	 * @param count the number of bytes to read
	 */
	public int readBytes( byte buf[], int start, int count )
	{
		int	bytes	= -1;
		
		if( open ) {
			bytes = count;
			
			if( count > currentRecord.length - pos ) {
				bytes = currentRecord.length - pos;
			}
			
			Vm.copyArray( currentRecord, pos, buf, start, bytes );
			pos += bytes;
		}
		
		return( bytes );
	}
	
	/**
	 * Advances the cursor in the current record a number of bytes. The cursor
	 * defines where read and write operations start from in the record. Returns
	 * the number of bytes actually skipped or -1 if an error occurs.
	 * @param count the number of bytes to skip
	 */
	public int skipBytes( int count )
	{
		int	bytes	= -1;
		
		if( open ) {
			pos += count;
			bytes = count;
		}
		
		return( bytes );
	}
	
	/**
	 * Writes to the current record. Returns the number of bytes written or -1
	 * if an error prevented the write operation from occurring.
	 * After the write is complete, the location of the cursor in the current record
	 * (where read and write operations start from) is advanced the number of bytes
	 * written.
	 * @param buf the byte array to write data from
	 * @param start the start position in the byte array
	 * @param count the number of bytes to write
	 */
	public int writeBytes( byte buf[], int start, int count )
	{
		int	bytes	= -1;
		
		if( open ) {
			Vm.copyArray( buf, start, currentRecord, pos, count );
			pos += count;
			bytes = count;
		}
		
		return( bytes );
	}
}